// GLFW MNet runtime.
//
// Copyright 2011-2012 Martin Leidel, all rights reserved.
//
// This software is provided 'as-is', without any express or implied
// warranty.  In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

#pragma comment( lib, "ws2_32.lib" )

#include <map>
#include <string>
//#include <utility>
//#include <windows.h>
//#include <winsock2.h>

static SOCKET _socket;
static SOCKADDR_IN _addr;
static std::map< char*, SOCKET > _clients;
static char* _lastClient = 0;
static char _buffer[1024];

class MNet_Socket
{
  public:
  
  static long Init()
  {
    WSADATA wsa;
    long result = WSAStartup( MAKEWORD( 2, 2 ), &wsa );
    if( result != 0 )
      Print( "WSAStartup failed!" );
    return result;
  }

  static long CreateServer( int port )
  {
    long result;

    // Create socket
    _socket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
    if( _socket == INVALID_SOCKET )
    {
      char err[256];
      sprintf( err, "Error while creating socket: %d", WSAGetLastError() );
      Print( err );
      return WSAGetLastError();
    }

    memset( &_addr, 0, sizeof( SOCKADDR_IN ) );
    _addr.sin_family = AF_INET;
    _addr.sin_port = htons( port );
    _addr.sin_addr.s_addr = INADDR_ANY;
    result = bind( _socket, (SOCKADDR*)&_addr, sizeof( SOCKADDR_IN ) );
    if( result == SOCKET_ERROR )
    {
      char err[256];
      sprintf( err, "Error while binding socket: %d", WSAGetLastError() );
      Print( err );
      WSACleanup();
      return WSAGetLastError();
    }

    result = listen( _socket, 32 );
    if( result == SOCKET_ERROR )
    {
      char err[256];
      sprintf( err, "Error while creating socket listener: %d", WSAGetLastError() );
      Print( err );
      return WSAGetLastError();
    }

    u_long iMode = 1;
    ioctlsocket( _socket, FIONBIO, &iMode );
    
    return result;
  }

  static void UpdateServer()
  {
    SOCKET s;
    // check for new connection from a client
    if( ( s = accept( _socket, NULL, NULL ) ) != INVALID_SOCKET )
    {
      Print( "Received connection from client" );
      // Create buffer for that client
      char* c = new char[1024];
      _clients.insert( std::pair< char*, SOCKET >( c, s ) );
      // remember that pointer for getting the last client
      _lastClient = c;
    }
    // iterate through all existing clients and receive their messages
    for( std::map< char*, SOCKET >::iterator it = _clients.begin(); it != _clients.end(); )
    {
      memset( it->first, 0, 1024 );
      int length = recv( it->second, it->first, 1024, 0 );
      int err = WSAGetLastError();
      if( err != WSAEWOULDBLOCK || length == 0 )
      {
        //shutdown( _clients[i].first, SD_SEND );
        char err[256];
        sprintf( err, "Error: %d", WSAGetLastError() );
        Print( err );
        Print( "A client disconnected" );
        closesocket( it->second );
        delete it->first;
        _clients.erase( it++ );
      }
      else
        ++it;
    }
  }

  static int GetNumberOfClients()
  {
    return _clients.size();
  }

  static int GetLastClient()
  {
    return reinterpret_cast<int>( _lastClient );
  }
  
  static int IsClientConnected( int id )
  {
    if( _clients.find( reinterpret_cast<char*>( id ) ) != _clients.end() )
      return 1;
    
    return 0;
  }

  static long SendBroadcast( String msg )
  {
    return Send( msg );
  }
  
  static long SendToClient( int id, String msg )
  {
    std::map< char*, SOCKET >::iterator it = _clients.find( reinterpret_cast<char*>( id ) );
    if( it != _clients.end() )
    {
      long result;
      result = send( it->second, msg.ToCString<char>(), msg.Length(), 0 );
      return result;
    }
    return -1;
  }
  
  ////////////////////////////////////////////////////////////////////////////
  static long ConnectToServer( String host, int port )
  {
    long result;

    // Create socket
    _socket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
    if( _socket == INVALID_SOCKET )
    {
      char err[256];
      sprintf( err, "Error while creating socket: %d", WSAGetLastError() );
      Print( err );
      WSACleanup();
      return WSAGetLastError();
    }

    // Resolve IP address for host name
    struct hostent *hostName;
    hostName = gethostbyname( host.ToCString<char>() );
    if( hostName == 0 )
    {
      Print( "Error while resolving hostname" );
      WSACleanup();
      return -1;
    }
    
    // Setup socket address structure
    memset( &_addr, 0, sizeof( SOCKADDR_IN ) );
    _addr.sin_family = AF_INET;
    _addr.sin_port = htons( port );
    _addr.sin_addr.s_addr = *( ( unsigned long* ) hostName->h_addr );
    
    // Set non-blocking mode
    u_long iMode = 1;
    ioctlsocket( _socket, FIONBIO, &iMode );
  
    // Attempt to connect to server
    // Will return immediately! You have to check the connection completeness via select!
    result = connect( _socket, (SOCKADDR*)&_addr, sizeof( SOCKADDR ) );
/*    if( result == SOCKET_ERROR )
    {
      WSACleanup();
      return WSAGetLastError();
    }*/
    
    return result;
  }

  static int IsConnected()
  {
    fd_set fsConnect;
    FD_ZERO( &fsConnect );
    FD_SET( _socket, &fsConnect );
    timeval timeout;
    timeout.tv_sec = (long)1;
    timeout.tv_usec = (long)0;

    // wait up to 1 second for the socket to complete connecting, unless
    // the target computer, network or internet is hanging this should return with 1 right away 
    int retval = select( FD_SETSIZE, (fd_set *)NULL, &fsConnect, (fd_set *)NULL, &timeout );
    if( retval != 1 )
    {
      // connect timed out
      Print( "Connection time out" );
      return 0;
    }
    return 1;
  }
  
  static long Send( String msg )
  {
    long result;
    result = send( _socket, msg.ToCString<char>(), msg.Length(), 0 );
    return result;
  }
  
  static String Receive()
  {
    memset( _buffer, 0, 1023 );
    int length = recv( _socket, _buffer, 1024, 0 );
    if( length != WSAEWOULDBLOCK && length != 0 )
    {
      //shutdown( _socket, SD_SEND );
      Print( "Disconnected" );
      closesocket( _socket );
      return String( "Disconnected" );
    }
    return String( _buffer );
  }
  
  static void Close()
  {
    // Free client buffers
    std::map< char*, SOCKET >::iterator it = _clients.begin();
    while( it != _clients.end() )
    {
      delete it->first;
      _clients.erase( it++ );
    }
    closesocket( _socket );
    WSACleanup();
  }
};
